/* Copyright (C) 2007-2010 STS Soft

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; version 2 of the License.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */

using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using STSdb.Files;
using STSdb.General.Compression;
using STSdb.General.Buffers;

namespace STSdb.Data
{
    public partial class FTable<TKey, TRecord>
    {
        private class ExpandedCollection : BaseCollection
        {
            private const ushort BRANCH_LENGTH = byte.MaxValue + 1;

            public LinkWrapper[] Items = new LinkWrapper[BRANCH_LENGTH];//!= null only these that have subtree

            static ExpandedCollection()
            {
                Debug.Assert(BRANCH_LENGTH % sizeof(byte) == 0);
            }

            public ExpandedCollection(Link parent)
                : base(parent)
            {
            }

            public override TRecord this[byte[] index]
            {
                get
                {
                    TRecord record;
                    if (!TryGet(index, out record))
                        throw new KeyNotFoundException("index");

                    return record;
                }
                set
                {
                    int idx = index[index.Length - Level - 1];
                    LinkWrapper wrapper = RefreshWrapper(idx, true);

                    wrapper.Link.Collection[index] = value;
                }
            }

            public override bool TryGet(byte[] index, out TRecord record)
            {
                int idx = index[index.Length - Level - 1];
                LinkWrapper wrapper = RefreshWrapper(idx, false);

                if (wrapper == null)
                {
                    record = default(TRecord);
                    return false;
                }

                return wrapper.Link.Collection.TryGet(index, out record);
            }

            public override void Delete(byte[] index)
            {
                int idx = index[index.Length - Level - 1];
                LinkWrapper wrapper = RefreshWrapper(idx, false);
                if (wrapper == null)
                    return;

                wrapper.Link.Collection.Delete(index);
            }

            public override void Modify(byte[] index)
            {
                int idx = index[index.Length - Level - 1];
                LinkWrapper wrapper = RefreshWrapper(idx, false);
                if (wrapper == null)
                    return;

                wrapper.Link.Collection.Modify(index);
            }

            public LinkWrapper RefreshWrapper(int idx, bool createIfNotExists)
            {
                LinkWrapper wrapper = Items[idx];

                if (wrapper != null)
                {
                    if (wrapper.Link != null)
                        wrapper.Link.Touch();
                    else
                    {
                        wrapper.Link = new Link(Owner.BufferManager, Owner, wrapper);
                        wrapper.Link.Load();
                    }
                }
                else
                {
                    if (!createIfNotExists)
                        return null;

                    wrapper = new LinkWrapper();
                    wrapper.Link = new Link(Owner.BufferManager, Owner, wrapper);
                    Items[idx] = wrapper;
                }

                return wrapper;
            }

            public override IEnumerable<Row<byte[], TRecord>> Forward(byte[] firstIndex, byte[] lastIndex)
            {
                bool first = true;
                for (int idx = firstIndex[firstIndex.Length - Level - 1]; idx < BRANCH_LENGTH; idx++)
                {
                    LinkWrapper wrapper = RefreshWrapper(idx, false);
                    if (wrapper == null)
                    {
                        first = false;
                        continue;
                    }

                    if (!first)
                    {
                        foreach (var row in wrapper.Link.Collection.Forward(Owner.BufferManager.MinIndex, lastIndex))
                        {
                            row.Key[firstIndex.Length - Level - 1] = (byte)idx;
                            yield return row;
                        }
                    }
                    else
                    {
                        foreach (var row in wrapper.Link.Collection.Forward(firstIndex, lastIndex))
                        {
                            row.Key[firstIndex.Length - Level - 1] = (byte)idx;
                            yield return row;
                        }
                        first = false;
                    }
                }
            }

            public override IEnumerable<Row<byte[], TRecord>> Backward(byte[] lastIndex, byte[] firstIndex)
            {
                bool first = true;
                for (int idx = lastIndex[lastIndex.Length - Level - 1]; idx >= 0; idx--)
                {
                    LinkWrapper wrapper = RefreshWrapper(idx, false);
                    if (wrapper == null)
                    {
                        first = false;
                        continue;
                    }

                    if (!first)
                    {
                        foreach (var row in wrapper.Link.Collection.Backward(Owner.BufferManager.MaxIndex, firstIndex))
                        {
                            row.Key[lastIndex.Length - Level - 1] = (byte)idx;
                            yield return row;
                        }
                    }
                    else
                    {
                        foreach (var row in wrapper.Link.Collection.Backward(lastIndex, firstIndex))
                        {
                            row.Key[lastIndex.Length - Level - 1] = (byte)idx;
                            yield return row;
                        }
                        first = false;
                    }
                }
            }

            public override IEnumerator<Row<byte[], TRecord>> GetEnumerator()
            {
                for (int idx = 0; idx < BRANCH_LENGTH; idx++)
                {
                    LinkWrapper wrapper = RefreshWrapper(idx, false);
                    if (wrapper == null)
                        continue;

                    foreach (var row in wrapper.Link.Collection)
                    {
                        row.Key[Owner.BufferManager.Depth - Level - 1] = (byte)idx;
                        yield return row;
                    }
                }
            }

            public override void Save(MemoryStream stream)
            {
                BinaryWriter writer = new BinaryWriter(stream);

                DeltaCompression.Helper helper1 = new DeltaCompression.Helper();
                DeltaCompression.Helper helper2 = new DeltaCompression.Helper();
                List<long> list1 = new List<long>(Items.Length);
                List<long> list2 = new List<long>(Items.Length);

                int count = 0;
                byte[] map = new byte[BRANCH_LENGTH / sizeof(byte)];
                byte[] indexes = new byte[30];

                for (int i = 0; i < Items.Length; i++)
                {
                    LinkWrapper item = Items[i];
                    if (item == null)
                        continue;

                    if (item.Ptr.IsNull)
                        continue;

                    if (count < indexes.Length)
                        indexes[count] = (byte)i;

                    count++;
                    map.SetBit(i, 1);

                    Ptr ptr = Items[i].Ptr;
                    list1.Add((long)ptr.Position);
                    list2.Add((long)ptr.Size);
                    helper1.AddValue((long)ptr.Position);
                    helper2.AddValue((long)ptr.Size);
                }

                //don't touch if you don't understand
                if (count == 0)
                    return;

                writer.Write((byte)(count - 1));//!
                if (count <= indexes.Length) //write indexes
                    writer.Write(indexes, 0, count);
                else
                {
                    if (count < BRANCH_LENGTH) //write map - only if not full all
                        writer.Write(map);
                }

                DeltaCompression.CoreCompress(writer, list1, helper1);
                DeltaCompression.CoreCompress(writer, list2, helper2);
            }

            public override void Load(MemoryStream stream)
            {
                BinaryReader reader = new BinaryReader(stream);

                int count = reader.ReadByte() + 1;
                if (count <= 30) //read indexes
                {
                    byte[] indexes = reader.ReadBytes(count);

                    IList<long> list1 = DeltaCompression.CoreDecompress(reader);
                    IList<long> list2 = DeltaCompression.CoreDecompress(reader);

                    for (int i = 0; i < indexes.Length; i++)
                    {
                        Ptr ptr = new Ptr((ulong)list1[i], (ulong)list2[i]);
                        Items[indexes[i]] = new LinkWrapper(ptr, null);
                    }
                }
                else //read map
                {
                    if (count < BRANCH_LENGTH)
                    {
                        byte[] map = reader.ReadBytes(BRANCH_LENGTH / sizeof(byte));

                        IList<long> list1 = DeltaCompression.CoreDecompress(reader);
                        IList<long> list2 = DeltaCompression.CoreDecompress(reader);

                        int idx = 0;
                        for (int i = 0; i < Items.Length; i++)
                        {
                            if (map.GetBit(i) == 0)
                                continue;

                            Ptr ptr = new Ptr((ulong)list1[idx], (ulong)list2[idx]);
                            Items[i] = new LinkWrapper(ptr, null);
                            idx++;
                        }
                    }
                    else
                    {
                        IList<long> list1 = DeltaCompression.CoreDecompress(reader);
                        IList<long> list2 = DeltaCompression.CoreDecompress(reader);

                        for (int i = 0; i < Items.Length; i++)
                        {
                            Ptr ptr = new Ptr((ulong)list1[i], (ulong)list2[i]);
                            Items[i] = new LinkWrapper(ptr, null);
                        }
                    }
                }
            }
        }
    }
}
